﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;

namespace AppToolkit.WPF.Utils.Keyboard
{
    
    /// <summary>
    /// Allows registering global shortcut keys.
    /// </summary>
    public class KeyboardHandler : IDisposable
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool RegisterHotKey(IntPtr hWnd, int registrationID, int fsModifiers, int virtualKeyCode);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int registrationID);

        readonly Window _mainWindow;
        readonly WindowInteropHelper _host;
        int _id = 0;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="mainWindow"></param>
        [ImportingConstructor()]
        public KeyboardHandler([Import(ContractDefinitions.MainWindow)]Window mainWindow)
        {
            _mainWindow = mainWindow;
            _host = new WindowInteropHelper(_mainWindow);

            SetupHotKey(_host.Handle);
            ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage;

            mainWindow.Closing += (sender, args) => this.Dispose();
        }

        public void RegisterHotKey(KeyModifier modifier, Key key)
        {
            Interlocked.Increment(ref _id);
            Contract.Assert(_id > 0);

            var registration = new HotKeyRegistration(_id, UnregisterHotKey);
            int vkCode = KeyInterop.VirtualKeyFromKey(key);
            if (!RegisterHotKey(_host.Handle, GetType().GetHashCode(), (int)modifier, vkCode))
            {
                var innerEx = GetWin32Exception();
                throw new Exception("Failed to register hot key!", innerEx);
            }
        }

        private void UnregisterHotKey(int id)
        {
            if (!UnregisterHotKey(_host.Handle, id))
            {
                var innerEx = GetWin32Exception();
                throw new Exception("Failed to unregister hot key!", innerEx);
            }
        }

        private static Win32Exception GetWin32Exception()
        {
            var errCode = Marshal.GetLastWin32Error();
            var innerEx = new Win32Exception(errCode);
            return innerEx;
        }


        void ComponentDispatcher_ThreadPreprocessMessage(ref MSG msg, ref bool handled)
        {
            if (msg.message == KeyDefinitions.WM_HOTKEY)
            {
                
                handled = true;
            }
        }

        private void SetupHotKey(IntPtr handle)
        {
            RegisterHotKey(handle, GetType().GetHashCode(), 0, KeyDefinitions.VIRTUALKEYCODE_FOR_CAPS_LOCK);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            for (int i = 0; i < _id; i++)
            {
                try
                {
                    UnregisterHotKey(_host.Handle, i);
                }
                catch (Exception ex)
                {
                    var msg = "Unregistering HotKey failed:\r\n" + ex.ToString();
                    Trace.TraceError(msg);
                }    
            }
        }
    }

    public class HotKeyRegistration
    {
        private readonly int _registrationID;
        private readonly Action<int> _unregistrationHook;

        internal HotKeyRegistration(int registrationID, Action<int> unregistrationHook)
        {
            Contract.Requires(unregistrationHook != null);

            _registrationID = registrationID;
            _unregistrationHook = unregistrationHook;
        }

        public void Unregister()
        {
            _unregistrationHook(_registrationID);
        }

        public event EventHandler<EventArgs> HotKeyPressed;
    }
}
